package org.bultreebank.index.schema.management.impl;

import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;

import org.bultreebank.index.schema.management.interfaces.IndexContextsManager;
import org.bultreebank.index.schema.management.interfaces.IndexingSchemaManager;
import org.bultreebank.index.utils.Constants;
import org.bultreebank.index.utils.Pair;
import org.bultreebank.index.utils.Utils;
import org.w3c.dom.Document;
import org.w3c.dom.Element;
import org.w3c.dom.NamedNodeMap;
import org.w3c.dom.Node;
import org.w3c.dom.NodeList;

import clark.xpath.MainXPath;


/**
 * Default Implementation of IndexContextsInterface
 * @author Aneliya Tincheva
 *
 */
public class IndexContextsImpl implements IndexContextsManager, Constants{

	private IndexingSchemaManager indexSchemasManager;
	private static final String METADATA_DOC_DOCTYPE = "metadata";
	private static final String CONTEXTS_XPATH = "//context";
	private static final String REAL_INDEX_ID = "realIndexID";
	private static final String CONTEXTS = "//context";
	
	private Document  indexSchemaDoc;
	
	public IndexContextsImpl(IndexingSchemaManager manager) throws Exception
	{
		indexSchemasManager = manager;
	}
	
	public void setIndexSchemaDocument(Document schemaDoc)
	{
		indexSchemaDoc = schemaDoc;
	}
	/**
	 * Adds field elements for the metadata fields as global context,
	 * the field elements have no "value" attribute
	 * @param metadataDoc - document containing metadata fields
	 * @param indexSchema -  normalized index schema document
	 * @throws Exception
	 */
	public void addMetadataContextFields(Document metadataDoc)
	throws Exception
	{
		
		if (!isMetadataDocValid(metadataDoc))
			throw new Exception("The metadata document is NOT valid!");	
		Element metaRoot = metadataDoc.getDocumentElement();
		NamedNodeMap attrs = metaRoot.getAttributes();
		List<String> globalFieldsNames = getGlobalFieldsNames();
		for (int i = 0; i < attrs.getLength(); i++)
		{
			 Node attr = attrs.item(i);
			 String attrName = attr.getNodeName();
			 if (!globalFieldsNames.contains(attrName))
			 {
				 Element field = createMetaFieldElem(attrName, indexSchemaDoc);
				 indexSchemaDoc.getDocumentElement().appendChild(field);
			 }
		}
		String indexID = (indexSchemaDoc.getDocumentElement()).getAttribute(NAME);
		indexSchemasManager.updateIndexSchema(indexSchemaDoc, indexID);
	}
	
	
	/**
	 * Returns a list of all  indexable (have realIndexID attribute)
	 * context elements for a normalized index schema document
	 * @param indexSchema -  normalized index schema document 
	 * @return
	 * @throws Exception
	 */
	public List<Node> getIndexSchemaContexts() throws Exception
	{
		return MainXPath.processXPath(CONTEXTS_XPATH,
				indexSchemaDoc.getDocumentElement());
	}
	public List<Node> getIndexSchemaRootContexts() throws Exception
	{
		return MainXPath.processXPath(CONTEXT,
				indexSchemaDoc.getDocumentElement());
	}
	/**
	 * Returns a list of all the global fields
	 * @param indexSchema -  normalized index schema document
	 * @return
	 * @throws Exception
	 */
	public List<SearchEngineField> getGlobalFields()
	throws Exception
	{
		Element root = indexSchemaDoc.getDocumentElement();
		String query = LEFT_SLASH + FIELD;
		List<Node> fieldNodes = MainXPath.processXPath(query, root);
		List<SearchEngineField> globalFields = new ArrayList<SearchEngineField>();
		for (Node node:fieldNodes)
		{
			SearchEngineField globalField = createField(node);
			globalFields.add(globalField);
		}
		return globalFields;
	}

	private List<String> getGlobalFieldsNames()
	{
		List<String> globalFieldsNames = new ArrayList<String>();
		Element root = indexSchemaDoc.getDocumentElement();
		String query = LEFT_SLASH + FIELD;
		List<Node> fieldNodes = MainXPath.processXPath(query, root);
		for (Node node: fieldNodes)
			globalFieldsNames.add(((Element)node).getAttribute(NAME));
		return globalFieldsNames;
	}
	/**
	 * Returns a Map with the context IDs as keys and the retrievable fields
	 * defined in the index schema document for these fields
	 * @param indexSchema -  normalized index schema document
	 * @return
	 * @throws Exception
	 */
	public Map<String, List<String>> getContextRetrievableFieldsRealIDs()
	throws Exception{
		return getContextRealIDsFieldsTypes(RETRIEVABLE);
	}
	public List<String> getGlobalRetrievableFields() throws Exception
	{
		List<SearchEngineField> globalFields = getGlobalFields();
		List<String> retrievableFields = new ArrayList<String>();
		for (SearchEngineField field: globalFields)
		{
			if (field.IsRetrievable() == true)
				retrievableFields.add(field.getName());
		}
		return retrievableFields;
	}
	public List<String> getGlobalSearchableFields() throws Exception
	{
		List<SearchEngineField> globalFields = getGlobalFields();
		List<String> searchableFields = new ArrayList<String>();
		for (SearchEngineField field: globalFields)
		{
			if (field.IsSearchable() == true)
				searchableFields.add(field.getName());
		}
		return searchableFields;
	}
	/**
	 * Returns a Map with the context IDs as keys and the searchable fields
	 * defined in the index schema document for these fields 
	 * @param indexSchema -  normalized index schema document
	 * @return
	 * @throws Exception
	 */
	public Map<String, List<String>> getContextSearchableFieldsRealIDs()
	throws Exception{
		return getContextRealIDsFieldsTypes(SEARCHABLE);
	}


	private Map<String, List<String>> getContextRealIDsFieldsTypes(String type)
	throws Exception{
		List<Node> contexts = getIndexSchemaContexts();
		Map<String, List<String>> map = new HashMap<String, List<String>>();
		for (Node context:contexts)
		{
			Element contextElem = (Element)context;
			String key = contextElem.getAttribute(REAL_INDEX_ID);
			NodeList children = contextElem.getChildNodes();
			List<String> fields = new ArrayList<String>();
			List<Node> retrievableFields = Utils.getAllNodesFromNodeList(YES, children,
					type);
			retrievableFields.addAll(Utils.getAllNodesFromNodeList(YES, children, "keyword"));
			for (Node retrievableField: retrievableFields)
				fields.add(((Element)retrievableField).getAttribute(NAME));
			map.put(key, fields);
		}
		return map;
	} 
	
	
	private SearchEngineField createField(Node node)
	{
		Element fieldElem = (Element) node;
		String name = fieldElem.getAttribute(NAME);
		String retrievable = fieldElem.getAttribute(RETRIEVABLE);
		String searchable = fieldElem.getAttribute(SEARCHABLE);
		String xpath = fieldElem.getAttribute(VALUE);
		String keyword = fieldElem.getAttribute(KEYWORD);
		SearchEngineField globalField = new SearchEngineField(name, retrievable,
				searchable,
				keyword, xpath);
		return globalField;
	}
	private boolean isMetadataDocValid(Document metadataDoc)
	{
		Element metadataRoot = metadataDoc.getDocumentElement();
		boolean isMetadataDocValid = true;
		if (!metadataRoot.getNodeName().equals(METADATA_DOC_DOCTYPE))
			isMetadataDocValid = false;
		NodeList children = metadataRoot.getChildNodes(); 
		if (children != null && children.getLength() > 0)
			isMetadataDocValid = false;
		return isMetadataDocValid;
	}
	private Element createMetaFieldElem(String name, Document parentDoc)
	{
		Element field = parentDoc.createElement(FIELD); 
		field.setAttribute(NAME, name);
		field.setAttribute(KEYWORD,YES);
		
		return field;
	}

	public Map<String, String> getContextInternalIDsMap()
	{
		Map <String, String> contexts = new HashMap<String, String>();
		Element root = indexSchemaDoc.getDocumentElement();
		List contextNodes = MainXPath.processXPath(CONTEXTS,root);
		for (int i = 0; i < contextNodes.size(); i++)
		{
			Element contextElem = (Element)contextNodes.get(i); 
			String xpath = contextElem.getAttribute(XPATH_ATTRIBUTE);
			String name = contextElem.getAttribute(NAME);
			if (!contexts.containsKey(xpath))
				contexts.put(xpath, name);
		}
		return contexts;
	}
	public Pair<String, String> getContextIDs(String searchIn) throws Exception
	{
		Element root = indexSchemaDoc.getDocumentElement();
		String xpath = "//context[@indexID!=\"\"]";
		List<Node> extracted = MainXPath.processXPath(xpath, root);
		if (extracted.isEmpty())
			throw new Exception("There are no contexts to search in.");
		//int contextDepth = 0;
		for (Node node: extracted)
		{
			Element elem = (Element)node;
			String name = elem.getAttribute(NAME);
			if (name.equals(searchIn))
			{
				return new Pair<String, String>(elem.getAttribute(INDEX_ID),
							elem.getAttribute(REAL_INDEX_ID));
			}
		}
		return null;
	}

	public String getRealContextID(String userDefinedContextID)
	throws Exception
	{
		List<Node> contextNodes = getIndexSchemaContexts();
		for (Node n: contextNodes)
		{
			Element contextElem = (Element) n;
			if (contextElem.getAttribute(NAME).equals(userDefinedContextID))
				return contextElem.getAttribute(INDEX_ID);
		}
		return EMPTY_STR;
	}

	@Override
	public Map<String, String> getContextFieldIDs(String contextID)
	{
		String xpath = null;
		if (contextID.equals(GLOBAL_CONTEXT))
			xpath = "./" + FIELD;
		else 
			xpath = "//context[@realIndexID=\"" + contextID + "\"]" + "/field";
		Element schemaDoc = indexSchemaDoc.getDocumentElement();
		List<Node> nodes = MainXPath.processXPath(xpath, schemaDoc);
		Map<String, String> result = new HashMap<String, String>();
		for (Node node: nodes)
		{
			Element elem = (Element)node;
			String name = elem.getAttribute(NAME);
			String keyword = elem.getAttribute(KEYWORD);
			String isSearchable = elem.getAttribute("searchable");
			if (keyword.equals(YES))
				result.put(name, "keyword");
			if (isSearchable.equals(YES))
				result.put(name, "searchable");
		}
		if (!result.containsKey(DOC_ID))
			result.put(DOC_ID, "keyword");
		return result;
	}
}
